package cn.lx.log.core.handler;


import cn.lx.log.core.bizcode.BizCodeMapper;
import cn.lx.log.core.context.UserContext;
import cn.lx.log.core.context.UserContextHolder;
import cn.lx.log.core.entity.LogContext;
import cn.lx.log.core.entity.LogInfo;
import cn.lx.log.core.entity.WrapperResponse;
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.UUID;

/**
 * 日志处理类
 */
@Aspect
public class LogHandler {


    private static final Logger logger = LoggerFactory.getLogger(LogHandler.class);

    private static final String CALL_FROM_CONTROLLER = "controller";

    @Value("${spring.application.name}")
    private String applicationName;
    @Value("${trace.areaCode}")
    private String areaCode;
    @Value("${trace.defaultBizCode}")
    private String defaultBizCode;
    @Value("${isRecordParam:true}")
    private boolean log_collection_parameter;
    @Autowired
    BizCodeMapper bizCodeMapper;

    @Value("${log.aspect}")
    private String aspectExpression;

    private static ThreadLocal<LogInfo> logInfoHolder = new ThreadLocal();

    public LogHandler() {
    }

    private String getAspectExpression(){
        return aspectExpression;
    }

    @Pointcut("")
    public void pointcut() {
    }



    /**
     * 获取请求IP地址
     * @param request
     * @return
     */
    private static String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }

        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }

        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
    }

    /**
     * 生成追踪ID
     * 生成规则 应用名称+"-"+uuid
     * @return
     */
    private String generateTraceID() {
        UUID uuid = UUID.randomUUID();
        if (this.applicationName != null && this.applicationName.length() > 10) {
            this.applicationName = this.applicationName.substring(0, 10);
        }

        return this.applicationName + "-" + uuid;
    }

    /**
     * 拦截controller层  前置通知
     * @param joinPoint
     */
    public void doControllerBefore(JoinPoint joinPoint) {
        logger.debug("LogHandler.doControllerBefore");
        try {
            LogInfo logInfo = new LogInfo();
            LogContext logContext = (LogContext)UserContextHolder.getContext().getProperty("logContext");
            if (logContext == null) {
                logInfo.setTraceID(this.generateTraceID());
            } else {
                logInfo.setTraceID(logContext.getTraceID());
            }

            MDC.put("traceID", logInfo.getTraceID());
            logInfo.setRequestTime((new SimpleDateFormat("yyyyMMddHHmmssSSS")).format(Calendar.getInstance().getTime()));
            this.packHttpLogInfo(logInfo, joinPoint);
            logInfo.setCallFrom(CALL_FROM_CONTROLLER);
            this.doBefore(logInfo);
        } catch (Exception e) {
            logger.error("LogHandler.doControllerBefore.exception", e);
        }

    }

    /**
     * 拦截controller层方法执行 返回通知
     * @param ret
     */
    public void doControllerAfterReturning(Object ret) {
        logger.debug("LogHandler.doControllerAfterReturning");

        try {
            LogInfo logInfo = (LogInfo)logInfoHolder.get();
            if (logInfo == null) {
                return;
            }

            doAfterReturning(ret, logInfo);
        } catch (Exception var3) {
            logger.error("LogHandler.doControllerAfterReturning.exception", var3);
        }

    }

    private static void doAfterReturning(Object ret, LogInfo logInfo) {
        logger.debug("====[response data]====");
        logger.info(logInfo.getLogString());
        MDC.remove("traceID");
    }



    /**
     * 填充日志信息
     * @param logInfo
     * @param joinPoint
     */
    private void packHttpLogInfo(LogInfo logInfo, JoinPoint joinPoint) {
        //获取servlet请求对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null && attributes.getRequest() != null) {
            HttpServletRequest request = attributes.getRequest();
            //设置请求url
            logInfo.setRequestUrl(request.getRequestURL().toString());
            //设置请求uri
            logInfo.setRequestUri(request.getRequestURI());
            //设置客户端ip
            logInfo.setClientIP(getIpAddr(request));
            //设置请求方法
            logInfo.setRequestMethod(request.getMethod());
            //设置类方法
            logInfo.setClassMethod(joinPoint.getSignature().getDeclaringTypeName()+"."+joinPoint.getSignature().getName());
            logInfo.setClazz(joinPoint.getSignature().getDeclaringType().getSimpleName());
            //如果设置收集请求参数
            if (this.log_collection_parameter && joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
                String requestParams = "";
                logger.debug("request.getMethod()=" + request.getMethod() + ";request.getContentType()=" + request.getContentType());
                //如果请求方法是一个GET请求,那么直接把请求参数数组转为字符串
                if ("GET".equalsIgnoreCase(request.getMethod())) {
                    requestParams = Arrays.toString(joinPoint.getArgs());
                }else {
                    try {
                        if (request.getContentType() == null || request.getContentType().indexOf("multipart/form-data") == -1) {
                            requestParams = JSONObject.toJSONString(joinPoint.getArgs()[0]);
                        }
                    }catch (Exception e){
                            requestParams = "";
                    }

                }
                logInfo.setRequestParams(requestParams);
            }
        }
    }

    /**
     * 拦截controller层方法,环绕通知  记录话费时间
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    public Object doControllerAround(ProceedingJoinPoint joinPoint) throws Throwable {

        logger.debug("LogHandler.doControllerAround");
        long startTime = System.currentTimeMillis();
        Object object = joinPoint.proceed();

        try {
            LogInfo logInfo = (LogInfo)logInfoHolder.get();
            if (logInfo == null) {
                return object;
            }

            doAround(startTime, logInfo);
        } catch (Exception e) {
            logger.error("LogHandler.doControllerAround.exception", e);
        }

        return object;
    }

    private static void doAround(long startTime, LogInfo logInfo) throws Throwable {
        long timeConsuming = System.currentTimeMillis() - startTime;
        logger.debug("[timeConsuming:] " + timeConsuming);
        logInfo.setTimeConsuming(timeConsuming);
    }




    private void doBefore(LogInfo logInfo) {
        logger.debug("====[request data]====");
        logger.debug(logInfo.toString());
        logger.debug(logInfo.getLogString());
        logInfoHolder.set(logInfo);
        UserContextHolder.getContext().addProperty("logContext",generateLogContext(logInfo));
    }

    /**
     * 生成日志上下文
     * 为了保证我们进行远程调用时，这一条日志基本信息可以不变，所以需要上下文进行远程调用传递。
     * @param logInfo
     * @return
     */
    private LogContext generateLogContext(LogInfo logInfo) {
        LogContext logContext = new LogContext();
        logContext.setTraceID(logInfo.getTraceID());
        boolean hasLogContext = false;
        //如果当前已经有日志上下文信息,把当前日志信息中的区划code、业务code、app签名改为上一条日志信息
        if (UserContextHolder.getContext().getProperties().containsKey("logContext")) {
            LogContext preLogContext = (LogContext)UserContextHolder.getContext().getProperty("logContext");
            if (preLogContext != null && preLogContext.getBizCode() != null) {
                logContext.setAreaCode(preLogContext.getAreaCode());
                logContext.setBizCode(preLogContext.getBizCode());
                logContext.setAppSign(preLogContext.getAppSign());
                hasLogContext = true;
            }
        }

        if (!hasLogContext) {
            this.packTraceInfo(logContext, logInfo.getRequestUri(), logInfo.getClazz());
        }

        return logContext;
    }

    private void packTraceInfo(LogContext logContext, String url, String ClassName) {
        logContext.setAreaCode(this.areaCode);
        logContext.setBizCode(this.bizCodeMapper.transBizCode(url) == null ? this.defaultBizCode : this.bizCodeMapper.transBizCode(url));
        logContext.setAppSign(ClassName == null ? "L" : (ClassName.startsWith("L_") ? "L" : "N"));
    }







}
